<script lang="ts" setup>
import FileAPI from "@api/file";

const imgUrl = ref("");
const canvas = ref();
let ctx: CanvasRenderingContext2D;

// 正在绘制中，用来控制 move 和 end 事件
let painting = false;

// 获取触发点相对被触发dom的左、上角距离
const getOffset = (event: MouseEvent | TouchEvent) => {
	let offset: [number, number];
	if ((event as MouseEvent).offsetX) {
		// pc端
		const {offsetX, offsetY} = event as MouseEvent;
		offset = [offsetX, offsetY];
	} else {
		// 移动端
		const {top, left} = canvas.value.getBoundingClientRect();
		const offsetX = (event as TouchEvent).touches[0].clientX - left;
		const offsetY = (event as TouchEvent).touches[0].clientY - top;
		offset = [offsetX, offsetY];
	}

	return offset;
};

// 绘制起点
let startX = 0,
	startY = 0;

// 鼠标/触摸 按下时，保存 触发点相对被触发dom的左、上 距离
const onEventStart = (event: MouseEvent | TouchEvent) => {
	[startX, startY] = getOffset(event);
	painting = true;
};

const onEventMove = (event: MouseEvent | TouchEvent) => {
	if (painting) {
		// 鼠标/触摸 移动时，保存 移动点相对 被触发dom的左、上 距离
		const [endX, endY] = getOffset(event);
		paint(startX, startY, endX, endY, ctx);

		// 每次绘制 或 清除结束后，起点要重置为上次的终点
		startX = endX;
		startY = endY;
	}
};

const onEventEnd = () => {
	if (painting) {
		painting = false; // 停止绘制
	}
};

onMounted(() => {
	ctx = canvas.value.getContext("2d") as CanvasRenderingContext2D;
});
const handleToFile = async () => {
	if (isCanvasBlank(canvas.value)) {
		ElMessage({
			type: "warning",
			message: "当前签名文件为空",
		});
		return;
	}
	const file = dataURLtoFile(canvas.value.toDataURL(), "签名.png");

	if (!file) return;
	const data = await FileAPI.upload(file);
	handleClearSign();
	imgUrl.value = data.url;
};
const handleClearSign = () => {
	ctx.clearRect(0, 0, canvas.value.width, canvas.value.height);
};
const isCanvasBlank = (canvas: HTMLCanvasElement) => {
	const blank = document.createElement("canvas"); //系统获取一个空canvas对象
	blank.width = canvas.width;
	blank.height = canvas.height;
	return canvas.toDataURL() == blank.toDataURL(); //比较值相等则为空
};

// 保存为图片
const handleSaveImg = () => {
	if (isCanvasBlank(canvas.value)) {
		ElMessage({
			type: "warning",
			message: "当前签名文件为空",
		});
		return;
	}
	const el = document.createElement("a");
	// 设置 href 为图片经过 base64 编码后的字符串，默认为 png 格式
	el.href = canvas.value.toDataURL();
	el.download = "签名";
	// 创建一个点击事件并对 a 标签进行触发
	const event = new MouseEvent("click");
	el.dispatchEvent(event);
};
// 转为file格式，可传递给后端
const dataURLtoFile = (dataurl: string, filename: string) => {
	const arr: string[] = dataurl.split(",");
	if (!arr.length) return;

	const mime = arr[0].match(/:(.*?);/);
	if (mime) {
		const bstr = atob(arr[1]);
		let n = bstr.length;
		const u8arr = new Uint8Array(n);
		while (n--) {
			u8arr[n] = bstr.charCodeAt(n);
		}
		return new File([u8arr], filename, {type: mime[1]});
	}
};

// canvas 画图
function paint(
	startX: number,
	startY: number,
	endX: number,
	endY: number,
	ctx: CanvasRenderingContext2D
) {
	ctx.beginPath();
	ctx.globalAlpha = 1;
	ctx.lineWidth = 2;
	ctx.strokeStyle = "#000";
	ctx.moveTo(startX, startY);
	ctx.lineTo(endX, endY);
	ctx.closePath();
	ctx.stroke();
}
</script>
<template>
	<div class="canvas-dom">
		<h3>基于canvas实现的签名组件</h3>
		<header>
			<el-button type="primary" @click="handleSaveImg">保存为图片</el-button>
			<el-button @click="handleToFile"> 保存到后端</el-button>
			<el-button @click="handleClearSign"> 清空签名</el-button>
		</header>
		<canvas
			ref="canvas"
			height="200"
			width="500"
			@mousedown="onEventStart"
			@mouseup="onEventEnd"
			@touchend="onEventEnd"
			@touchstart="onEventStart"
			@mousemove.stop.prevent="onEventMove"
			@touchmove.stop.prevent="onEventMove"
		>
		</canvas>
		<img v-if="imgUrl" :src="imgUrl" alt="签名"/>
	</div>
</template>
<style lang="scss" scoped>
.canvas-dom {
	width: 100%;
	height: 100%;
	padding: 0 20px;
	background-color: #fff;

	canvas {
		border: 1px solid #e6e6e6;
	}

	header {
		display: flex;
		flex-flow: row nowrap;
		align-items: center;
		width: 100%;
		margin: 8px;

		.eraser-option {
			display: flex;

			label {
				white-space: nowrap;
			}
		}
	}
}
</style>
